using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

using PostSharp.Laos;
using EntlibAOP.Diagnostic.Logging;
using System.Diagnostics;
using System.Messaging;

namespace EntlibAOP.Diagnostic.Tracing
{
    /// <summary>
    /// Trace target execution aspect
    /// </summary>
    [Serializable]
    public class TraceApplicationAttribute : OnMethodBoundaryAspect
    {
        private string methodName;
        private string logMessage;
        private string[] category;
        private bool batchLogging;
        private int batchSize = 100;
        private TraceExecutionEvent traceOptions = TraceExecutionEvent.Entry | TraceExecutionEvent.Exit;
        /// <summary>
        /// Trace message extended properties
        /// </summary>
        private IDictionary<string, object> extProperties;
        

        #region Properties
        /// <summary>
        /// Trace message
        /// </summary>
        public string Message
        {
            get { return logMessage; }
            set { logMessage = value; }
        }
        /// <summary>
        /// Trace message categories
        /// </summary>
        public string[] Categories
        {
            get { return category; }
            set { category = value; }
        }
        /// <summary>
        /// Trace message extended properties
        /// </summary>
        public IDictionary<string, object> ExtendedProperties
        {
            get { return extProperties; }
            set { extProperties = value; }
        }
        /// <summary>
        /// Type of logging that will be applied
        /// </summary>
        public TraceExecutionEvent TraceExecutionOptions
        {
            get { return traceOptions; }
            set { traceOptions = value; }
        }

        /// <summary>
        /// Enable th batch logging feature
        /// </summary>
        public bool BatchLogging
        {
            get
            {
                return batchLogging;
            }
            set
            {
                batchLogging = value;
            }
        }

        /// <summary>
        /// Set the size of log queue
        /// </summary>
        public int BatchSize
        {
            get
            {
                return batchSize;
            }
            set
            {
                batchSize = value;
            }
        }
        #endregion

        #region Overloaded Methods

        public override void CompileTimeInitialize(MethodBase method)
        {
            base.CompileTimeInitialize(method);
            methodName = method.DeclaringType.Name + "." + method.Name;
        }

        /// <summary>
        /// The code to be woven into the start of the method. 
        /// This will be run before any of the other visible code in the method. 
        /// </summary>
        /// <param name="eventArgs">Argument passed to the event handler</param>
        public override void OnEntry(MethodExecutionEventArgs eventArgs)
        {
            if ((traceOptions & TraceExecutionEvent.Entry) == TraceExecutionEvent.Entry )
            {                
                //building the parameter string
                string paraStr = BuildParameterString(eventArgs.Method.GetParameters(), 
                    eventArgs.GetReadOnlyArgumentArray());

                if (paraStr != null)
                {
                    //get the parameter string

                    LogToSink(this.category, this.extProperties, "Entering " + methodName
                        + Environment.NewLine + paraStr,TraceEventType.Information);
                }
                else
                {
                    LogToSink(this.category, this.extProperties, "Entering " + methodName, TraceEventType.Information);
                }                
            }
        }

        /// <summary>
        /// Ensure the sucessful completion of the method is logged
        /// </summary>
        /// <param name="eventArgs">Argument passed to the event handler</param>
        public override void OnSuccess(MethodExecutionEventArgs eventArgs)
        {
            if ((traceOptions & TraceExecutionEvent.Success) == TraceExecutionEvent.Success)
            {
                LogToSink(this.category, this.extProperties, "Sucessfully completed " + methodName
                    + (eventArgs.ReturnValue != null?Environment.NewLine +eventArgs.ReturnValue.ToString():null),TraceEventType.Information);
            }
        }

        /// <summary>
        /// Ensure any unhandled exceptions are logged also.
        /// </summary>
        /// <param name="eventArgs">Argument passed to the event handler</param>
        public override void OnException(MethodExecutionEventArgs eventArgs)
        {
            if ((traceOptions & TraceExecutionEvent.Exception) == TraceExecutionEvent.Exception)
            {
                StringBuilder message = new StringBuilder();
                message.Append("Method:").Append(this.methodName).Append(Environment.NewLine);
                message.Append("Arguments:").Append(Environment.NewLine);

                //building the parameter string
                string paraStr = BuildParameterString(eventArgs.Method.GetParameters(),
                    eventArgs.GetReadOnlyArgumentArray());                
                if(paraStr != null)
                    message.Append(paraStr);

                message.Append("Exception:").Append(Environment.NewLine);
                message.Append(eventArgs.Exception.Source).Append(Environment.NewLine);
                message.Append(eventArgs.Exception.Message).Append(Environment.NewLine);
                message.Append(eventArgs.Exception.StackTrace).Append(Environment.NewLine);

                LogToSink(this.category, this.extProperties, message.ToString(),TraceEventType.Error);

                /// Rethrow the exception
                /// TODO: We might want to match this with the behavior of ExceptionHandlerAttribute 
                eventArgs.FlowBehavior = FlowBehavior.RethrowException;
            }
        }

        /// <summary>
        /// The code to be woven into the end of the method. This will be run before exiting the method.
        /// </summary>
        /// <param name="eventArgs">Argument passed to the event handler</param>
        public override void OnExit(MethodExecutionEventArgs eventArgs)
        {
            if ((traceOptions & TraceExecutionEvent.Exit) == TraceExecutionEvent.Exit )
            {
                //building the parameter string
                string paraStr = BuildParameterString(eventArgs.Method.GetParameters(),
                    eventArgs.GetReadOnlyArgumentArray());

                //Adding the log message to record method exit
                
                if (paraStr != null)
                {
                    LogToSink(this.category, this.extProperties, "Exiting" + methodName
                        + Environment.NewLine + paraStr,TraceEventType.Information);
                }
                else
                {
                    LogToSink(this.category, this.extProperties, "Exiting" + methodName, TraceEventType.Information);
                }
                
            }
        }
        #endregion

        #region Private method

        /// <summary>
        /// This method will take the parameters and its value and construct the display string
        /// </summary>
        /// <param name="parameters">Collection of parameter names</param>
        /// <param name="arguments">Collection of parameter values</param>
        /// <returns>Display string</returns>
        private string BuildParameterString(ParameterInfo[] parameters, object[] arguments)
        {
            StringBuilder message = new StringBuilder();
            if(parameters != null && arguments != null)
            {
                message.Append(Environment.NewLine);
                message.Append("Parameter Values:");
                //Loop through the parameter collection and argument collection to add
                //parameters and its values to the log message
                for (int i = 0; i < parameters.Length; i++)
                {
                    message.Append(parameters[i].Name).Append("=").
                        Append(arguments[i]).Append(Environment.NewLine);
                }
                return message.ToString();
            }
            return null;
            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="q"></param>
        /// <param name="cursor"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        private Message PeekWithoutTimeout(MessageQueue q, Cursor cursor, PeekAction action)
        {
            Message ret = null;
            try
            {
                ret = q.Peek(new TimeSpan(1), cursor, action);
            }
            catch (MessageQueueException mqe)
            {
                if (!mqe.Message.ToLower().Contains("timeout"))
                {
                    throw;
                }
            }
            return ret;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="q"></param>
        /// <returns></returns>
        private int GetMessageCount(MessageQueue q)
        {
            int count = 0;
            Cursor cursor = q.CreateCursor();

            Message m = PeekWithoutTimeout(q, cursor, PeekAction.Current);
            if (m != null)
            {
                count = 1;
                while ((m = PeekWithoutTimeout(q, cursor, PeekAction.Next)) != null)
                {
                    count++;
                }
            }
            return count;
        }

        private void LogToSink (string[] currCategory, IDictionary<string, object> currProperties, string currMessage, TraceEventType messageType)
        {
            if (batchLogging)
            {
                MessageQueue oMq;
                //Check if the queue is available else create a new queue
                if (!MessageQueue.Exists(@".\Private$\EntlibAOPLoggingQueue"))
                {
                    oMq = MessageQueue.Create(@".\Private$\EntlibAOPLoggingQueue");
                }
                else
                {
                    oMq = new MessageQueue(@".\Private$\EntLibAOPLoggingQueue");
                }

                if (messageType == TraceEventType.Error)
                {
                    oMq.Send(currMessage, "Error");
                }
                else
                {
                    oMq.Send(currMessage, "Information");
                }

                int msgCount = GetMessageCount(oMq);
                if (msgCount >= batchSize)
                {
                    StringBuilder logMessages = new StringBuilder();
                    StringBuilder errorMessages = new StringBuilder();
                    oMq.Formatter = new System.Messaging.XmlMessageFormatter(new string[] { "System.String" });
                    Message[] messages = oMq.GetAllMessages();
                    foreach (Message msg in messages)
                    {
                        if (msg.Label == "Information")
                        {
                            logMessages.Append(msg.Body.ToString() + Environment.NewLine);
                        }   
                        else if (msg.Label == "Error")
                        {
                            errorMessages.Append(msg.Body.ToString() + Environment.NewLine);
                        }                       
                    }
                    oMq.Purge();
                    if (logMessages.Length > 0)
                    {
                        Logger.WriteInformation(new string[] { "Bulk Log" }, logMessages.ToString());
                    }
                    if (errorMessages.Length > 0)
                    {
                        Logger.WriteError(new string[] { "Bulk Log" }, errorMessages.ToString());
                    }
                }

            }
            else
            {
                if (messageType == TraceEventType.Error)
                {
                    Logger.WriteError(currCategory, currProperties, currMessage);
                }
                else
                {
                    Logger.WriteInformation(currCategory, currProperties, currMessage);
                }
            }
        }
        #endregion
    }


}
